package com.avaliacao.util;

import java.io.IOException;
import java.util.Collection;

import javax.el.ELContext;
import javax.faces.FactoryFinder;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.primefaces.context.RequestContext;

public final class FacesUtil {

	private static final Logger LOGGER = Logger.getLogger(FacesUtil.class);

	private FacesUtil() {
		throw new UnsupportedOperationException();
	}

	/* FacesContext utilities */

	private static FacesContext getFacesContext() {
		return FacesContext.getCurrentInstance();
	}

	/**
	 * Add information message.
	 * 
	 * @param message
	 *            the information message
	 */
	public static void addInfoMessage(String message) {
		LOGGER.debug(message);
		getFacesContext().addMessage(null, new FacesMessage(
				FacesMessage.SEVERITY_INFO, "Sucesso", message));
	}

	/**
	 * Add warning message.
	 * 
	 * @param message
	 *            the warning message
	 */
	public static void addWarningMessage(String message) {
		LOGGER.debug(message);
		getFacesContext().addMessage(null, new FacesMessage(
				FacesMessage.SEVERITY_WARN, "Atenção", message));
	}

	/**
	 * Add error message.
	 * 
	 * @param message
	 *            the error message
	 */
	public static void addErrorMessage(String message) {
		LOGGER.debug(message);
		getFacesContext().addMessage(null, newErrorMessage(message));
	}

	/**
	 * Create new error message.
	 * 
	 * @param message
	 *            the error message
	 * @return the faces message
	 */
	public static FacesMessage newErrorMessage(String message) {
		LOGGER.debug(message);
		return new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro", message);
	}

	/**
	 * Add fatal error message.
	 * 
	 * @param message
	 *            the fatal error message
	 */
	public static void addFatalMessage(String message) {
		LOGGER.debug(message);
		getFacesContext().addMessage(null, new FacesMessage(
				FacesMessage.SEVERITY_FATAL, "Erro crítico", message));
	}

	/**
	 * Get a value by evaluating an expression associated with this web
	 * application.
	 * 
	 * @param expression
	 *            the expression to evaluate
	 * @param type
	 *            the expected return type
	 * @return the expression value
	 */
	public static <T> T evaluateExpression(String expression, Class<? extends T> type) {
		FacesContext facesContext = getFacesContext();
		T result = facesContext.getApplication().evaluateExpressionGet(
				facesContext, "#{" + expression + "}", type);
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format("expression:%s type:%s result:%s",
					expression, type, result));
		}
		return result;
	}

	/**
	 * Returns managed bean by the given name. The bean must be registered
	 * either per faces-config.xml or annotation. Injection is good, but
	 * sometimes if beans are rare called, it’s not necessary to inject beans
	 * into each other.
	 * 
	 * @param name
	 *            the bean name
	 * @return the managed bean associated with the bean name
	 */
	public static <T> T getManagedBean(String name, Class<? extends T> type) {
		return getManagedBean(getFacesContext(), name, type);
	}

	@SuppressWarnings("unchecked")
	private static <T> T getManagedBean(FacesContext facesContext,
			String name, Class<? extends T> type) {
		T bean = null;
		if (facesContext != null) {
			ELContext elContext = facesContext.getELContext();
			bean = (T) elContext.getELResolver().getValue(elContext, null, name);
		}
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format(
					"name:%s type:%s bean:%s", name, type, bean));
		}
		return bean;
	}

	public static <T> T getManagedBean(HttpServletRequest request,
			HttpServletResponse response, String name, Class<? extends T> type) {
		FacesContext facesContext = getFacesContext(request, response);
		return getManagedBean(facesContext, name, type);
	}

	private static FacesContext getFacesContext(
			HttpServletRequest request, HttpServletResponse response) {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		if (facesContext == null) {
			LifecycleFactory lifecycleFactory = (LifecycleFactory)
					FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
			Lifecycle lifecycle =
					lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

			FacesContextFactory contextFactory = (FacesContextFactory)
					FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
			facesContext = contextFactory.getFacesContext(
					request.getSession().getServletContext(), request, response, lifecycle);

			UIViewRoot viewRoot =
					facesContext.getApplication().getViewHandler().createView(facesContext, "");
			facesContext.setViewRoot(viewRoot);

			FacesContextWrapper.setCurrentInstance(facesContext);
		}
		return facesContext;
	}

	private static abstract class FacesContextWrapper extends FacesContext {
		protected static void setCurrentInstance(FacesContext facesContext) {
			FacesContext.setCurrentInstance(facesContext);
		}
	}

	/**
	 * Return true if the renderResponse() method has been called for the
	 * current request.
	 * 
	 * @return
	 */
	public static boolean getRenderResponse() {
		boolean renderResponse = getFacesContext().getRenderResponse();
		LOGGER.debug(renderResponse);
		return renderResponse;
	}

	/* ExternalContext utilities */

	private static ExternalContext getExternalContext() {
		return getFacesContext().getExternalContext();
	}

	/**
	 * Get parameter value from request scope.
	 * 
	 * @param key
	 *            the key of the parameter
	 * @return the parameter value
	 */
	public static String getRequestParameter(Object key) {
		String value = getExternalContext().getRequestParameterMap().get(key);
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format("key:%s value:%s", key, value));
		}
		return value;
	}

	/**
	 * Redirect a request to the specified URL, and cause the responseComplete()
	 * method to be called on the FacesContext instance for the current request.
	 * 
	 * @param url
	 *            absolute URL to which the client should be redirected
	 * @throws IOException
	 *             if an input/output error occurs
	 */
	public static void redirect(String url) throws IOException {
		LOGGER.debug(url);
		ExternalContext externalContext = getExternalContext();
		externalContext.redirect(externalContext.getRequestContextPath() + url);
	}

	/**
	 * Return the environment-specific object instance for the current request.
	 * 
	 * @return the current request instance
	 */
	public static HttpServletRequest getServletRequest() {
		HttpServletRequest request =
				(HttpServletRequest) getExternalContext().getRequest();
		LOGGER.debug(request);
		return request;
	}

	/**
	 * Return the environment-specific object instance for the current response.
	 * 
	 * @return the current request's response instance
	 */
	public static HttpServletResponse getServletResponse() {
		HttpServletResponse response =
				(HttpServletResponse) getExternalContext().getResponse();
		LOGGER.debug(response);
		return response;
	}

	/**
	 * Return the login name of the user making the current request if any;
	 * otherwise, return null.
	 * 
	 * @return the login name of current user
	 */
	public static String getRemoteUser() {
		String remoteUser = getExternalContext().getRemoteUser();
		LOGGER.debug(remoteUser);
		return remoteUser;
	}

	/**
	 * If the create parameter is true, create (if necessary) and return a
	 * session instance associated with the current request. If the create
	 * parameter is false return any existing session instance associated with
	 * the current request, or return null if there is no such session.
	 * 
	 * @param create
	 *            flag indicating whether or not a new session should be created
	 * @return session instance associated with the current request
	 */
	public static HttpSession getSession(boolean create) {
		HttpSession session = null;
		if (getFacesContext() != null) {
			session = (HttpSession) getExternalContext().getSession(create);
		}
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format(
					"create:%s session:%s", create, session));
		}
		return session;
	}

	/**
	 * Return true if the currently authenticated user is included in the
	 * specified role. Otherwise, return false.
	 * 
	 * @param role
	 *            logical role name to be checked
	 * @return if the currently authenticated user is included in the specified
	 *         role
	 */
	public static boolean isUserInRole(String role) {
		boolean userInRole = getExternalContext().isUserInRole(role);
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format(
					"role:%s userInRole:%s", role, userInRole));
		}
		return userInRole;
	}

	/**
	 * Signal the JavaServer Faces implementation that the HTTP response for
	 * this request has already been generated (such as an HTTP redirect), and
	 * that the request processing lifecycle should be terminated as soon as the
	 * current phase is completed.
	 */
	public static void responseComplete() {
		LOGGER.debug(null);
		getFacesContext().responseComplete();
	}

	/**
	 * Returns a String containing the real path for a given virtual path.
	 * 
	 * @param path
	 *            the context of the requested initialization parameter
	 * @return the real path for a given virtual path
	 */
	public static String getRealPath(String path) {
		String realPath = getExternalContext().getRealPath(path);
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format("path:%s realPath:%s", path, realPath));
		}
		return realPath;
	}

	@SuppressWarnings("unchecked")
	public static <T> T removeManagedBean(String name, Class<? extends T> type) {
		return (T) removeManagedBean(name);
	}

	public static Object removeManagedBean(String name) {
		Object bean = getExternalContext().getSessionMap().remove(name);
		LOGGER.debug(String.format("name:%s bean:%s", name, bean));
		return bean;
	}

	/* RequestContext utilities */

	private static RequestContext getRequestContext() {
		return RequestContext.getCurrentInstance();
	}

	/**
	 * Execute a javascript after current ajax request is completed.
	 * 
	 * @param script
	 *            JavaScript statement to execute.
	 */
	public static void requestExecute(String script) {
		LOGGER.debug(script);
		getRequestContext().execute(script);
	}

	/**
	 * Update a component with ajax.
	 * 
	 * @param name
	 *            client side identifier of the component.
	 */
	public static void requestUpdate(String name) {
		LOGGER.debug(name);
		getRequestContext().update(name);
	}

	/**
	 * Update components with ajax.
	 * 
	 * @param collection
	 *            client side identifiers of the components.
	 */
	public static void requestUpdate(Collection<String> collection) {
		LOGGER.debug(collection);
		getRequestContext().update(collection);
	}

	/**
	 * Add a parameter for ajax oncomplete client side callbacks. Value would be
	 * serialized to json.
	 * 
	 * @param name
	 *            name of the parameter
	 * @param value
	 *            value of the parameter
	 */
	public static void addCallbackParam(String name, Object value) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.format("name:%s value:%s", name, value));
		}
		getRequestContext().addCallbackParam(name, value);
	}

}
